iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
Modern Web

三十天成為D3.js v7 好手系列 第 21

Day21-D3 基礎圖表:散點圖/散佈圖

  • 分享至 

  • xImage
  •  

本篇大綱:基本散佈圖範例、進階散佈圖範例

今天的一天一圖表,我們要來畫 散點圖 / 散佈圖!散佈圖適合用在以下幾種情況:

  • 了解資料的分布狀況
  • 比較不同資料的分布狀況

https://ithelp.ithome.com.tw/upload/images/20211003/20134930WoXFkv33Fb.jpg

除了呈現資料之外,也可以加上一些互動效果

  • 滑鼠點擊時加上點點
  • hover 時圓點變色+呈現座標

https://i.imgur.com/aVB7BQG.gif

今天我們就用這兩個範例來實際繪製散點圖吧!


基本散點圖範例

我們先來建立一個最基本的散佈圖。一般來說,這種散佈圖的資料都是 raw data,就是幾百幾單一、沒有經過統計的資料。這樣一來才能將所有的單筆資料變成一個點,並於散佈圖呈現所有點點的分佈情況。

我們今天使用的資料是 這個 ,確定資料後就可以開始畫圖啦!首先,我們先用async await 的方法串接API,拿到我們的資料;接著起手式一樣是建立RWD的svg圖表

// css
.scatter1 {
      margin: auto;
      width: 80%;
      min-width: 300px;
      margin: auto;
    }
// html
<div class="scatter1 m-auto"></div>
// 先取資料
async function getData(){
  let dataGet;

  const cors = "https://secret-ocean-49799.herokuapp.com/";
  const url = 'https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv'

  dataGet = await d3.csv(`${cors}${url}`);
  scatter1(dataGet)
}
getData()

// 建立圖表
function scatter1(data){
  // RWD 清除原本的圖型
  d3.select(".scatter1").select('svg').remove()

  // 建立 svg
  const svgWidth = parseInt(d3.select('.scatter1').style('width')),
        svgHeight = svgWidth
        margin = 50
  const svg = d3.select('.scatter1')
    .append('svg')
    .attr('width', svgWidth)
    .attr('height', svgHeight)

// 接下來的程式碼...
// 接下來的程式碼...
// 接下來的程式碼...
}

// RWD
d3.select(window).on("resize", scatter1());

建立好 svg 後,我們開始繪製比例尺跟XY座標軸

// 建立X軸線
const xScale = d3.scaleLinear()
       .domain([0,4000])
       .range([0, (svgWidth - margin*2)])

const xAxis = d3.axisBottom(xScale)
                

svg.append('g')
   .attr('transform', `translate(${margin}, ${svgHeight - margin/2})`)
   .call(xAxis)

// 建立Y軸線
console.log(data)
const yScale = d3.scaleLinear()
       .domain([0,500000])
       .range([(svgHeight - margin), 0])

const yAxis = d3.axisLeft(yScale)    
                .tickFormat(d=>'$'+d) 

svg.append('g')
   .attr('transform', `translate(${margin}, ${margin/2})`)
   .call(yAxis)

最後我們把資料綁定到 < circle >上

// 加上點點
svg.append('g')
   .selectAll('dot')
   .data(data)
   .enter()
     .append('circle')
     .attr('cx', d => xScale(d.GrLivArea))
     .attr('cy', d => yScale(d.SalePrice))
     .attr('r', 1.5)
     .style('fill', '#69b3a2')

這樣就完成啦!
https://ithelp.ithome.com.tw/upload/images/20211003/20134930D3eX5tqNd6.jpg

如果我們想把特定範圍的資料設定成不同顏色,也可以這樣做

// 加上點點
svg.append('g')
   .selectAll('dot')
   .data(data)
   .enter()
     .append('circle')
     .attr('cx', d => xScale(d.GrLivArea))
     .attr('cy', d => yScale(d.SalePrice))
     .attr('r', 1.5)
   // 將大於129000的點點都變成粉紅色
     .style('fill', d => {
       return d.SalePrice > 129000? 'pink':'#69b3a2' 
     })

圖表就變成這樣!
https://ithelp.ithome.com.tw/upload/images/20211003/20134930xZlXOUqEsG.jpg

這樣就完成啦!散佈圖是不是很簡單呢~接著我們再看看範例二的圖表吧!


範例二的畫面與互動效果

範例二畫面與互動效果有:

  • XY 座標軸以及 < circle > 畫出的散佈圓點,比例尺可以用 scaleLinearscalePoint 製作
  • 滑鼠滑過跟移開時,圓點變色(mouseover & mouseleave )
  • 滑鼠 hover 時呈現圓點座標 (d3.pointer)
  • 滑鼠點擊時,加上新的圓點

我們就按照這些列出效果來繪製散點圖吧!首先,我們先建立 svg 跟座標軸

// css
.scatter1 {
      margin: auto;
      width: 80%;
      min-width: 300px;
      margin: auto;
    }
// html
<div class="scatter2 m-auto"></div>
function scatter2(){
// 建立svg
let w = parseInt(d3.select(".scatter2").style("width")),
    h = w*0.8,
    margin = {
        top: 40,
        right: 20,
        bottom: 20,
        left: 40
        },
    radius = 5;

let svg = d3.select(".scatter2").append("svg").attr('width', w).attr('height', h);
console.log(w, h)

let dataset = [
    { x: 100,
      y: 110 },
    { x: 83,
      y: 43 },
    { x: 92,
      y: 28 },
    { x: 49,
      y: 74 },
    { x: 51,
      y: 10 },
    { x: 25,
      y: 98 },
    { x: 77,
      y: 30},
    { x: 20,
      y: 83 },
    { x: 11,
      y: 63 },
    { x: 4,
      y: 55 },
    { x: 0,
      y: 0 },
    { x: 85,
      y: 100 },
    { x: 60,
      y: 40 },
    { x: 70,
      y: 80 },
    { x: 10,
      y: 20 },
    { x: 40,
      y: 50 },
    { x: 25,
      y: 31 }
];

// 接下來的程式碼寫在這邊...
// 接下來的程式碼寫在這邊...
// 接下來的程式碼寫在這邊...

}

// RWD
d3.select(window).on("resize", scatter2());

接著建立比例尺與座標軸

// 建立X比例尺與軸線
const xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) {
        return d.x + 10;
    })])
    .range([margin.left, w - margin.right]);

const xAxis = d3.axisTop()
    .scale(xScale)

const xAxisLine = svg.append('g')
    .attr('class', 'xAxis')
    .attr('transform', `translate(0, ${margin.top})`)
    .call(xAxis)

// 建立Y比例尺與軸線
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function (d) {
    return d.y + 10;
})])
.range([margin.top, h - margin.bottom])

const yAxis = d3.axisLeft()
    .scale(yScale)

const yAxisLine = svg.append('g')
    .attr('class', 'yAxis')
    .attr('transform', `translate(${margin.left}, 0)`)
    .call(yAxis)

建立好軸線跟比例尺後,開始把資料綁定到 < circle >上

// 資料綁定上circle
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr('cx', function (d) {
        return xScale(d.x);
    })
    .attr('cy', function (d) {
        return yScale(d.y);
    })
    .attr('r', 5)
    .attr('fill', '#000')
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut);

這時我們就會得到初步的散點圖表了
https://ithelp.ithome.com.tw/upload/images/20211003/20134930zBEknBEnsz.jpg

再來,我們來進行滑鼠的互動設定吧~先來設定 mouseover 跟 mouseleave 的方法

//  mouseover 時點點變色+tooltip
function handleMouseOver(d, i) {
    // 選定this的元素,改變hover過去的顏色跟形狀
    d3.select(this)
        .attr('fill', 'orange')
        .attr('r', radius * 2);

    // 加上tooltips
    let pt = d3.pointer(event, this)
    svg.append("text")
        .attr('class', 'hoverTextInfo')
        .attr('x', pt[0] + 10)
        .attr('y', pt[1] - 10)
        .style('fill', 'red')
        .text([`x:${d.x}, y:${d.y}`])
}

// mouseleave 時變回原樣
function handleMouseOut(d, i) {
    d3.selectAll('.hoverTextInfo').remove()
    d3.select(this)
        .attr('fill', 'black')
        .attr('r', radius)
}

接著設定滑鼠點擊時,要紀錄點擊當下的座標,並將這個座標用 .invert 的方法換算成資料,再把這個新增的資料用push的方法推入原本的資料陣列

// 滑鼠click的時候增加一個點
svg.on("click", function () {
let coords = d3.pointer(event, this);

let newData = {
  // 把XY座標軸轉換成資料
    x: Math.round(xScale.invert(coords[0])),
    y: Math.round(yScale.invert(coords[1]))
};

// 將增加的資料座標推入原本的data
dataset.push(newData);

最後,我們要重新把更新過後的資料綁定到DOM上,同時把滑鼠over跟leave的方法也綁定上去

// 將新的資料綁定上circle、綁定 mouseover/mouseleave 方法
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr('cx', function (d) {
        return xScale(d.x);
    })
    .attr('cy', function (d) {
        return yScale(d.y);
    })
    .attr('r', 5)
    .attr('fill', '#000')
    .on("mouseover", handleMouseOver)
    .on("mouseout", handleMouseOut);
})

完成!!
https://i.imgur.com/aVB7BQG.gif


Github Page 圖表與 Github 程式碼

這邊附上本章的程式碼與圖表 GithubGithub Page,需要的人請自行取用~


上一篇
Day20-D3 基礎圖表:圓餅圖
下一篇
Day22-D3 基礎圖表:長條圖
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言